#

## Apple II Weather Display (part 2)

posted Apr 19th 2011 8:01am by Kevin Dady
filed under: misc hacks, software hacks

In part one of the Apple II weather display I quickly went over how data is fetched and phrased. Now its time to do something with it in part 2. In the order of functions I do the text parts first, and though its very similar to the process that the radar image goes through, its in monochrome and a bit simpler to explain. Before I go into how it works I should explain how I am dividing the Apple II’s screen.

The high resolution mode on an Apple II gives you 280×160 with 4 lines of text on the bottom, or 280×192 full screen. I will be using 280×192 full screen, because as useful as the text can be, it also would show the endless stream of gibberish while updating, and since the entire color video mode of the Apple II design is a NTSC hack, its not all that pretty on a standard TV while also displaying color graphics. I also divide the screen into “blocks” 35 pixels wide each, this came about due to my encoding system.

Without getting into high ASCII or special characters I decided to use a base 36 type numbering system, we only need 35 unique characters to represent any pixel on a line within a block. The value of 1 means the first pixel in a given block and so on to Z being pixel number 35 in a block, and up to 8 blocks for the entire screen. Block separators and other functions can be sent using lower case characters, making a nice n easy plain text system. For example, if the apple received bbb123Z it means skip to block 4 and place a pixel at 1,2,3, and 35 within that block (and if there is no pixels at all for that given line a n is sent).

Looking at text.lua …

    -- Weather Underground to Apple //
    -- 2011 Kevin Dady
    --
    -- Text to Graphics:
    -- take text from web.data
    -- make text images with image magick
    -- phrase *.xpm files to apple //
    -- send text images
    -- end
    text = {}
    text.data = {}
    text.data.input  = {}
    text.data.packed = {{},{},{},{}}
    text.data.apl2   = {{},{},{}}

    text.createIMG = function()
    if web.data[1] == nil then
    web.data[1] = "No Advisories"
    end
    if web.data[3] == nil then
    web.data[3] = web.data[7]
    end
    -- create the bottom text
    cmd.imageMGK(" -background black -fill white -font req/VeraMoBd.ttf"..
    " -dither none -map req/mono.xpm -size 279x53 -pointsize 11 -gravity West"..
    " caption:'".. web.data[2].."\n"..web.data[6].."\n"..web.data[5].."\n"..web.data[3].. "'"..
    " temp/textBTM.xpm")
    -- create the text for advisories
    cmd.imageMGK(" -background black -fill white -font req/VeraMoBd.ttf"..
    " -dither none -map req/mono.xpm -size 139x64 -pointsize 11 -gravity Center"..
    " caption:'".. web.data[1].."'"..
    " temp/textALT.xpm")
    -- create the text for temperature
    cmd.imageMGK(" -background black -fill white -font req/VeraMoBd.ttf"..
    " -dither none -map req/mono.xpm -size 139x74 -pointsize 36 -gravity Center"..
    " caption:'".. web.data[4].."'"..
    " temp/textTMP.xpm")
    end

    text.convertIMG = function()
    local files  = {"textTMP.xpm","textALT.xpm","textBTM.xpm"}
    local footer = {82,72,61}
    local width  = {140,140,280}
    -- read the files into a table one at a time
    for img = 1, 3 do
    local file = io.open("temp/".. files[img],"r")
    table.insert(text.data.input, img, {})
    for line in file:lines() do
    table.insert(text.data.input[img], tostring(line))
    end
    file:close()
    -- remove header
    for y = 1, 7 do
    table.remove(text.data.input[img], 1)
    end
    -- remove footer
    table.remove(text.data.input[img], footer[img] - 7)
    -- remove non pixel data
    for y = 1, #text.data.input[img] do
    text.data.input[img][y] = string.sub(text.data.input[img][y], 2, width[img])
    end
    end
    end

    text.sortIMG = function()
    local newChar = ""
    for img = 1, 3 do
    -- need to convert the strings into tables
    for y = 1, #text.data.input[img] do
    table.insert(text.data.apl2[img], {})
    -- for each column in the current row
    for x = 1, #text.data.input[img][y] do
    -- read the character at that Y,X point
    newChar = string.sub(text.data.input[img][y], x,x)
    if newChar == "." then
    table.insert(text.data.apl2[img][y], x) -- pixel
    end
    end
    end
    end
    end

    text.packageIMG = function()
    for img = 1, 2 do
    for y = 1, #text.data.input[img] do
    local one   = ""
    local two   = ""
    local three = ""
    local four  = ""
    for x = 1, #text.data.apl2[img][y] do
    if text.data.apl2[img][y][x] <= 35 then
    one = one .. string.sub(graphicsKey, text.data.apl2[img][y][x], text.data.apl2[img][y][x])
    elseif text.data.apl2[img][y][x] <= 70 then
    two = two .. string.sub(graphicsKey, text.data.apl2[img][y][x] - 35, text.data.apl2[img][y][x] - 35)
    elseif text.data.apl2[img][y][x] <= 105 then
    three = three .. string.sub(graphicsKey, text.data.apl2[img][y][x] - 70, text.data.apl2[img][y][x] - 70)
    else
    four = four .. string.sub(graphicsKey, text.data.apl2[img][y][x] - 105, text.data.apl2[img][y][x] - 105)
    end
    end
    if one == "" and two == "" and three == "" and four == "" then
    table.insert(text.data.packed[img], "n")
    else table.insert(text.data.packed[img],one.."b"..two.."b"..three.."b"..four)
    end
    end
    end
    for y = 1, #text.data.input[3] do
    local one   = ""
    local two   = ""
    local three = ""
    local four  = ""
    local five  = ""
    local six   = ""
    local seven = ""
    local eight = ""
    for x = 1, #text.data.apl2[3][y] do
    if text.data.apl2[3][y][x] <= 35 then
    one = one .. string.sub(graphicsKey, text.data.apl2[3][y][x], text.data.apl2[3][y][x])
    elseif text.data.apl2[3][y][x] <= 70 then
    two = two .. string.sub(graphicsKey, text.data.apl2[3][y][x] - 35, text.data.apl2[3][y][x] - 35)
    elseif text.data.apl2[3][y][x] <= 105 then
    three = three .. string.sub(graphicsKey, text.data.apl2[3][y][x] - 70, text.data.apl2[3][y][x] - 70)
    elseif text.data.apl2[3][y][x] <= 140 then
    four = four .. string.sub(graphicsKey, text.data.apl2[3][y][x] - 105, text.data.apl2[3][y][x] - 105)
    elseif text.data.apl2[3][y][x] <=175 then
    five = five .. string.sub(graphicsKey, text.data.apl2[3][y][x] - 140, text.data.apl2[3][y][x] -140)
    elseif text.data.apl2[3][y][x] <= 210 then
    six = six .. string.sub(graphicsKey, text.data.apl2[3][y][x] - 175, text.data.apl2[3][y][x] - 175)
    elseif text.data.apl2[3][y][x] <= 245 then
    seven = seven .. string.sub(graphicsKey, text.data.apl2[3][y][x] - 210, text.data.apl2[3][y][x] - 210)
    else
    eight = eight .. string.sub(graphicsKey, text.data.apl2[3][y][x] - 245, text.data.apl2[3][y][x] - 245)
    end
    end
    if one == "" and two == "" and three == "" and four == "" and five == "" and six == "" and seven == "" and eight == "" then
    table.insert(text.data.packed[3], "n")
    else table.insert(text.data.packed[3],one.."b"..two.."b"..three.."b"..four.."b"..five.."b"..six.."b"..seven.."b"..eight)
    end
    end
    end

    text.sendIMG = function()
    for img = 1, 3 do
    for y = 1, #text.data.packed[img] do
    cmd.sjinn(text.data.packed[img][y])
    end
    cmd.sleep(2)
    end
    end

I start off in text.createIMG() by checking a couple things, One if there is any advisories, and if not place a No Advisories tag, Second looking for “windchill”, there is not always a “windchill” and if not place “dew point” in its place so we don’t have any blank lines. once were good to go we send the script off to imagemagick to make 3 graphics that contain black and white text. one for the long text at the bottom, one for advisories, and one for temperature.

text.convertIMG() reads the XPM files generated by imagemagick and does some cleanup. It starts by chopping the header and footer off of the image file, and removes the line formatting from each line of the image.

text.sortIMG() takes the leftover string data and scans each character in each line, in this case a white pixel is represented by a “.” (period) and a black pixel (which we don’t care about) is represented by a ” ” (space). Each time a period is found its x position is added to the end of a table. By the end of the image we are left with a table that has a subtable for each line, and contains X values for each pixel in each line, for example:

data= {}

data[1] = {1,2,12,80}

data[2] = {140,143,144,150}

There are 3 images to process, and they are different sizes, though how tall they are does not really matter to my script, its the width we are concerned about. text.packageIMG() reads each image and divides them up into blocks, the temperature text and advisory text are both 140 pixels wide and consume 4, 35 pixel blocks, so each value in each line is read out of our tables above and have some basic math done on them. If a value is greater than 35 for example then its block 2, the value has 35 subtracted from it and that is our block value (36 – 35 = block 2 pixel 1) . The bottom text is the widest graphic, taking up the entire width of the screen, but its just the same thing just spread over 8 blocks. Once we have our blocks as encoded strings they are packed into a single string per line of the image with “b” separating each block.

radar.lua does pretty much the same thing, except instead of making graphics it dithers the radar image downloaded earlier.

    -- Weather Underground to Apple //
    -- 2011 Kevin Dady
    --
    -- Radar processing:
    -- feed jpeg to image magick
    -- phrase output.xpm to color tables
    -- package for apple //
    -- send radar

    radar = {}

    radar.data = {}
    radar.data.input  = {}  -- raw file data table dump
    radar.data.packed = {}  -- packed apple data
    radar.data.apl2 = {{},{},{},{},{},{}} -- black, green, violet, orange, blue, white

    radar.img = {}
    radar.img.header = 11 -- xpm file header # of lines
    radar.img.w = 141
    radar.img.h = 141

    radar.convertIMG = function()

    cmd.imageMGK(" temp/radar.jpg -level 0%,70%,1 -dither none -map req/apple.xpm temp/output.xpm")
    -- read the file into a table
    local file = io.open("temp/output.xpm","r")
    for line in file:lines() do
    table.insert(radar.data.input, tostring(line))
    end
    file:close()

    -- remove header
    for i = 1, radar.img.header do -- hardcode
    table.remove(radar.data.input, 1)
    end

    -- remove footer
    table.remove(radar.data.input, radar.img.w)

    -- remove non color data
    for i = 1, #radar.data.input do
    radar.data.input[i] = string.sub(radar.data.input[i], 2, radar.img.h)
    end
    -- only deal with odd rows, due to the even / odd, bit / line, funny way apple 2's display highres colors.
    -- if we leave them all in the image there is a gret chance of 2 colors phasing into another,
    -- since we are going to loose pixel resolution anyway, we can cut that down some by deleting every other line
    -- giving 140x70 also making transfer size smaller.
    local keep ={}
    for i = 1, #radar.data.input do
    if (i % 2) == 1 then table.insert(keep, radar.data.input[i]) end
    end
    radar.data.input = keep
    end

    -- " " = 0 Apple color black (1)
    -- "X" = 1 Apple color green
    -- "o" = 2 Apple color violet
    -- "." = 5 Apple color orange
    -- "O" = 6 Apple color blue
    -- "+" = 7 Apple color white (2)

    radar.sortIMG = function()
    local newChar = ""
    -- need to convert the strings into tables
    for y = 1, #radar.data.input do
    -- add a new "line" string to each color table
    for color = 1, 6 do
    table.insert(radar.data.apl2[color], {})
    end
    -- for each column in the current row
    for x = 1, #radar.data.input[y] do
    -- read the character at that Y,X point
    newChar = string.sub(radar.data.input[y], x,x)
    -- assign each character a individual table value
    if newChar == " " then table.insert(radar.data.apl2[1][y], x) -- black
    elseif newChar == "X" then table.insert(radar.data.apl2[4][y], x) -- GREEN
    elseif newChar == "o" then table.insert(radar.data.apl2[3][y], x) -- violet
    elseif newChar == "." then table.insert(radar.data.apl2[2][y], x) -- ORANGE
    elseif newChar == "O" then table.insert(radar.data.apl2[5][y], x) -- blue
    elseif newChar == "+" then table.insert(radar.data.apl2[6][y], x) -- white
    end
    end
    end
    end

    radar.packageIMG = function()
    for color = 1, 5 do    -- ignore white, white takes a long time to draw since it makes up most of the graphic
    for y = 1, #radar.data.apl2[color] do
    local one   = ""
    local two   = ""
    local three = ""
    local four  = ""
    for x = 1, #radar.data.apl2[color][y] do
    if radar.data.apl2[color][y][x] <= 35 then
    one = one .. string.sub(graphicsKey, radar.data.apl2[color][y][x], radar.data.apl2[color][y][x])
    elseif radar.data.apl2[color][y][x] <= 70 then
    two = two .. string.sub(graphicsKey, radar.data.apl2[color][y][x] - 35, radar.data.apl2[color][y][x] - 35)
    elseif radar.data.apl2[color][y][x] <= 105 then
    three = three .. string.sub(graphicsKey, radar.data.apl2[color][y][x] - 70, radar.data.apl2[color][y][x] - 70)
    else
    four = four .. string.sub(graphicsKey, radar.data.apl2[color][y][x] - 105, radar.data.apl2[color][y][x] - 105)
    end
    end

    if one == "" and two == "" and three == "" and four == "" then
    table.insert(radar.data.packed, "n")
    else table.insert(radar.data.packed,one.."b"..two.."b"..three.."b"..four)
    end
    end
    end
    end

    radar.send = function()
    for line = 1, 350 do
    cmd.sjinn(radar.data.packed[line])
    end
    end

Imagemagick is used to dither the radar image to 6 of the 8 “available” colors, the Apple II only has 6 unique colors in high resolution mode, and the other 2 are black 2 and white 2. This has to do with how the apple does color, I mentioned earlier that its a hack, which basically uses bit patterns and the phase of the color burst to generate different colors. The end effect is always interesting as you can never place a specific color on each and every location on each and every line.

In order to help cut down on artifacts between lines and to cut the data transfer in half I then only use the even lines of the image, which yea reduces my radar resolution from 140×140 to 140×70 but due to the above mentioned wonkyness of the Apple’s video system I would have lost most of that resolution anyway.

The output XPM file has its header, footer and line formatting removed and each color is split up into individual tables, from there the process is the same, reading each monochrome image, packing it into block defined lines and packed up for the Apple to consume. I ignore white and just draw that on the screen as it makes up most of the image, saving time, and the end data is 5 copies of the image line / block encoded, each acting as a mask for its unique color.

For the most part were done looking at the lua scripts, so join me for part 3 (the end) where we will explore the software the apple II uses.

Part 1 and Part 3 are also available.

- Share this:

- Comments [1]

tagged: apple II, retro

### Reader Comments

It would be helpful if you put the link to part 1 in the text. Thanks.

Posted at 2:49 am on Apr 20th, 2011 by jef

### Leave a Reply

Name (required)

Mail (will not be published) (required)

Website

Notify me of follow-up comments via email.

Subscribe to this site by email

Hack a Day serves up fresh hacks each day, every day from around the web as well as hacking related news.

Send us your hacks

### Hacks

- android hacks (92)
- arduino hacks (488)
- Ask Hackaday (6)
- beer hacks (26)
- blackberry hacks (7)
- cellphones hacks (258)
- chemistry hacks (29)
- classic hacks (613)
- clock hacks (72)
- cnc hacks (145)
- cons (141)
- contests (83)
- cooking hacks (3)
- digital audio hacks (288)
- digital cameras hacks (260)
- downloads hacks (100)
- ds hacks (33)
- firefox hacks (21)
- g1 hacks (26)
- gameboy hacks (77)
- google hacks (42)
- gps hacks (79)
- green hacks (66)
- Hackaday links (49)
- Hackerspaces (17)
- HackIt (85)
- handhelds hacks (170)
- hardware (36)
- home entertainment hacks (431)
- home hacks (440)
- how-to (58)
- Interviews (1)
- iphone hacks (136)
- ipod hacks (131)
- Kinect hacks (29)
- laptops hacks (93)
- laser hacks (102)
- led hacks (348)
- lifehacks (19)
- linux hacks (63)
- lockpicking hacks (8)
- macs hacks (111)
- Medical hacks (32)
- Microcontrollers (140)
- misc hacks (1368)
- multitouch hacks (73)
- musical hacks (73)
- netbook hacks (49)
- news (746)
- nintendo hacks (197)
- parts (46)
- pcs hacks (304)
- peripherals hacks (516)
- phone hacks (12)
- playstation hacks (102)
- podcasts (8)
- portable audio hacks (63)
- portable video hacks (52)
- pre hacks (6)
- psp hacks (44)
- radio hacks (38)
- rants (9)
- repair hacks (30)
- reviews (12)
- robots hacks (551)
- roundup (28)
- security hacks (308)
- Software Development (16)
- software hacks (12)
- solar hacks (22)
- tablet pcs hacks (15)
- teardown (13)
- tool hacks (341)
- toy hacks (138)
- transportation hacks (274)
- Uncategorized (330)
- video hacks (87)
- weapons hacks (5)
- wearable hacks (111)
- wii hacks (69)
- wireless hacks (246)
- xbox hacks (110)

### Resources

- Send us news tips
- Contact us

### Most commented on (30 days)

- TI adds some Linux support for Evalbot - we've got hardware coupon codes for you! (109)
- Hackaday is looking for a full time project builder/ video host! (78)
- iPhone watching every breath you take, every move you make (77)
- Powering vehicles with aluminum (74)
- Hand-built car made almost entirely from scavenged parts (71)
- Headphones use standard-sized but proprietary rechargeable batteries (70)
- DARPA Needs Your Help! Design A Ground Combat Vehicle. (63)
- RFID drink system eliminates the need for change (60)
- DoJ and FBI now issuing command to botnet malware (58)
- Do your projects violate International Traffic in Arms Regulations? (53)

### Recent comments

- Max: @BGH: Not necessarily. There is
- Swankie: i agree with BGH. the
- Jacob: @turbochris I would think it would.
- tony: strider_mt2k knows whats up.
- strider_mt2k: How about the power to
- Whatnot: Oh and one more small
- macona: @rickroll, yes there is a
- Whatnot: Well the 'you can't see
- Bernhard: @cutthroughstuffguy: We have a workflow of
- infrared: lame... object tracking is nothing

Send to Email Address Your Name Your Email Address
Cancel
Post was not sent - check your email addresses!
Email check failed, please try again
Sorry, your blog cannot share posts by email.